﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Garfield.Core.Runtime;

namespace Garfield.Core
{
    [PluginDependencies(typeof(CorePlugin))]
    public abstract class Plugin
    {
        public class ExtensionPoint
        {
            internal ExtensionPoint()
            {
            }

            public string PluginIdentifier { get; internal set; }
            public string Identifier { get; internal set; }
            public bool IsCachingExtension { get; internal set; }
        }

        public sealed class Extension
        {
            internal Extension()
            {
            }

            public ExtensionPoint ExtensionPoint { get; internal set; }
            public object Identifier { get; internal set; }
            public object Instance { get; internal set; }
            public Type Implementation { get; internal set; }
            public Delegate Initializer { get; internal set; }
        }

        public sealed class ExtensionPoint<ExtensionType> : ExtensionPoint
        {
            internal ExtensionPoint()
            {
            }
        }

        public sealed class ExtensionPoint<IdentifierType, ExtensionType> : ExtensionPoint
        {
            internal ExtensionPoint()
            {
            }
        }

        protected static ExtensionPoint<ExtensionType> DeclareExtensionPoint<ExtensionType>(Type extensionPointHost, string identifier, bool isCahcingExtension)
        {
            return new ExtensionPoint<ExtensionType> {PluginIdentifier = GetIdentifier(extensionPointHost), Identifier = identifier, IsCachingExtension = isCahcingExtension};
        }

        protected static ExtensionPoint<IdentifierType, ExtensionType> DeclareExtensionPoint<IdentifierType, ExtensionType>(Type extensionPointHost, string identifier, bool isCahcingExtension)
        {
            return new ExtensionPoint<IdentifierType, ExtensionType> {PluginIdentifier = GetIdentifier(extensionPointHost), Identifier = identifier, IsCachingExtension = isCahcingExtension};
        }

        public static string IdentifierOf<T>() where T:Plugin
        {
            return GetIdentifier(typeof (T));
        }

        internal static string GetIdentifier(Type pluginType)
        {
            if (!pluginType.IsSubclassOf(typeof (Plugin)) || pluginType.IsAbstract)
                throw new ArgumentException(pluginType + " is not a proper plugin type.");
            var pluginAttribute =
                pluginType.GetCustomAttributes(typeof (PluginAttribute), false).Cast<PluginAttribute>().SingleOrDefault();
            return pluginAttribute != null ? pluginAttribute.Identifier : pluginType.FullName;
        }

        internal static ExtensionPoint[] GetExtensionPoints(Type pluginType)
        {
            var allStaticFields = pluginType.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly).Select(field => field.GetValue(null));
            return allStaticFields.Where(value => value is ExtensionPoint).Cast<ExtensionPoint>().ToArray();
        }

        internal static string[] GetDependencies(Type pluginType)
        {
            return
                pluginType.GetCustomAttributes(typeof (PluginDependenciesAttribute), true).Cast<PluginDependenciesAttribute>().SelectMany(dependencies => dependencies.PluginIdentifiers).Distinct().ToArray();
        }

        private readonly List<Extension> extensions = new List<Extension>();
        private readonly List<string> dependencies = new List<string>();

        protected Plugin()
        {
            var pluginType = GetType();
            Identifier = GetIdentifier(pluginType);
            ExtensionPoints = GetExtensionPoints(pluginType);
            dependencies.AddRange(GetDependencies(pluginType));
            Initialize();
            Extensions = extensions.ToArray();
            if (dependencies.Contains(Identifier))
            {
                dependencies.Remove(Identifier);
            }
            Dependencies = dependencies.ToArray();
        }

        public string Identifier { get; private set; }
        public ExtensionPoint[] ExtensionPoints { get; private set; }
        public string[] Dependencies { get; private set; }
        public Extension[] Extensions { get; private set; }

        protected void Register<ExtensionType, ImplementationType>(ExtensionPoint<ExtensionType> extensionPoint, Class<ImplementationType> implementation) where ImplementationType : ExtensionType
        {
            AddExtension(new Extension {ExtensionPoint = extensionPoint, Implementation = implementation.Type});
        }

        protected void Register<ExtensionType, ImplementationType>(ExtensionPoint<ExtensionType> extensionPoint, Class<ImplementationType> implementation, Action<ImplementationType> initializer) where ImplementationType : ExtensionType
        {
            AddExtension(new Extension { ExtensionPoint = extensionPoint, Implementation = implementation.Type, Initializer = initializer});
        }

        protected void Register<ExtensionType>(ExtensionPoint<ExtensionType> extensionPoint, ExtensionType instance)
        {
            AddExtension(new Extension {ExtensionPoint = extensionPoint, Instance = instance});
        }

        protected void Register<IdentifierType, ExtensionType, ImplementationType>(ExtensionPoint<IdentifierType, ExtensionType> extensionPoint, IdentifierType identifier, Class<ImplementationType> implementation) where ImplementationType : ExtensionType
        {
            AddExtension(new Extension {ExtensionPoint = extensionPoint, Identifier = identifier, Implementation = implementation.Type});
        }

        protected void Register<IdentifierType, ExtensionType, ImplementationType>(ExtensionPoint<IdentifierType, ExtensionType> extensionPoint, IdentifierType identifier, Class<ImplementationType> implementation, Action<ImplementationType> initializer) where ImplementationType : ExtensionType
        {
            AddExtension(new Extension { ExtensionPoint = extensionPoint, Identifier = identifier, Implementation = implementation.Type, Initializer = initializer});
        }

        protected void Register<ExtensionIdentifierType, ExtensionType>(ExtensionPoint<ExtensionIdentifierType, ExtensionType> extensionPoint, ExtensionIdentifierType identifier, ExtensionType instance)
        {
            AddExtension(new Extension {ExtensionPoint = extensionPoint, Identifier = identifier, Instance = instance});
        }

        protected void Register<ApiType, ImplementationType>(ExtensionPoint<Api, Implementation> extensionPoint, Class<ApiType> identifier, Class<ImplementationType> implementation)
            where ImplementationType : ApiType
        {
            AddExtension(new Extension { ExtensionPoint = extensionPoint, Identifier = identifier.Type, Implementation = implementation.Type });
        }

        protected void Register<ApiType, ImplementationType>(ExtensionPoint<Api, Implementation> extensionPoint, Class<ApiType> identifier, Class<ImplementationType> implementation, Action<ImplementationType> initializer)
            where ImplementationType : ApiType
        {
            AddExtension(new Extension { ExtensionPoint = extensionPoint, Identifier = identifier.Type, Implementation = implementation.Type, Initializer = initializer});
        }


        protected void Register<ApiType>(ExtensionPoint<Api, Implementation> extensionPoint, Class<ApiType> identifier, ApiType instance)
        {
            AddExtension(new Extension { ExtensionPoint = extensionPoint, Identifier = identifier.Type, Instance = instance });
        }

        protected void RegisterService<Api, Implementation>() where Implementation : Api
        {
            AddExtension(new Extension {ExtensionPoint = Platform.Services, Identifier = typeof(Api), Implementation = typeof(Implementation)});
        }

        protected void RegisterService<Api, Implementation>(Action<Implementation> initializer) where Implementation : Api
        {
            AddExtension(new Extension { ExtensionPoint = Platform.Services, Identifier = typeof(Api), Implementation = typeof(Implementation), Initializer = initializer});
        }

        protected void RegisterService<Api>(Api implementation)
        {
            AddExtension(new Extension { ExtensionPoint = Platform.Services, Identifier = typeof(Api), Instance = implementation });
        }

        private void AddExtension(Extension extension)
        {
            extensions.Add(extension);
            var pluginIdentifier = extension.ExtensionPoint.PluginIdentifier;
            if (pluginIdentifier != null && !dependencies.Contains(pluginIdentifier) && pluginIdentifier != Identifier)
                dependencies.Add(pluginIdentifier);
        }

        protected static Class<T> Api<T> ()
        {
            return new Class<T>();
        }

        protected static Class<T> Implementation<T>()
        {
            return new Class<T>();
        }

        protected abstract void Initialize();

        public virtual void Start(IServiceLocator serviceLocator) {}
        public virtual void Stop(IServiceLocator serviceLocator) {}

        internal struct ExtensionPointDeclaraion
        {
            public string PluginIdentifier { get; internal set; }
            public string Identifier { get; internal set; }
            public bool IsCachingExtension { get; internal set; }
        }
    }

    public sealed class Class<ImplementationType>
    {
        internal Class() {}

        public Type Type { get { return typeof (ImplementationType); }}
    }

    public sealed class Api { internal Api() {} }

    public sealed class Implementation { internal Implementation() {}}
}